
// Copyright (c) WanSheng Intelligent Corp. All rights reserved.
// Licensed under the LGPL v3.0 license. 


#include <stdlib.h>
#include "modbus_module.h"
#include "modbus_plugin.h"
#include "wa_plugin_sdk.h"


bool check_fc_read_write(int fc, bool yes_read)
{
    if (yes_read)
    {
        if (fc <= MODBUS_FC_READ_INPUT_REGISTERS || fc == MODBUS_FC_READ_EXCEPTION_STATUS)
            return true;
    }
    else
    {
        if (fc == MODBUS_FC_WRITE_SINGLE_COIL ||
            fc == MODBUS_FC_WRITE_SINGLE_REGISTER ||
            fc == MODBUS_FC_WRITE_MULTIPLE_COILS ||
            fc == MODBUS_FC_WRITE_MULTIPLE_REGISTERS)
            return true;
    }

    return false;
}



int get_reg_type_fc(mb_reg_type_e rt, bool write)
{
    if(write)
    {
        switch (rt)
        {
        case MB_Discrete:
            return -1;
        case MB_Coil:
            return MODBUS_FC_WRITE_SINGLE_COIL;
        case MB_Input:
            return -1;
        case MB_Holding:
            return MODBUS_FC_WRITE_SINGLE_REGISTER;
        
        default:
            break;
        }
    }
    else
    {
        switch (rt)
        {
        case MB_Discrete:
            return MODBUS_FC_READ_DISCRETE_INPUTS;
        case MB_Coil:
            return MODBUS_FC_READ_COILS;
        case MB_Input:
            return MODBUS_FC_READ_INPUT_REGISTERS;
        case MB_Holding:
            return MODBUS_FC_READ_HOLDING_REGISTERS;
        
        default:
            break;
        }
    }
    return -1;
}

static void handle_modbus_command(i_modbus_command_t * cmd, i_modbus_t * bus, REQ_ENV_HANDLE req_env)
{
    char *payload = NULL;
    int payload_len = 0;
    int rc = -1;
    int fmt = IA_TEXT_PLAIN;
    int response = CONTENT_2_05;

    // ensure all access in this function will be targetting the device slave address
    bus->current_slave_id =  cmd->slave_id;

    if (check_fc_read_write(cmd->fc, true))
    {
        payload = read_command_from_modbus(cmd, &fmt, &payload_len);
    }
    else
    {
        rc = write_command_to_modbus(cmd, fmt);
        response = CHANGED_2_04;
    }

    if(rc == -1 && payload == NULL)
    {
        response = INTERNAL_SERVER_ERROR_5_00;
        payload = (char*) malloc(256);
        if(payload)
        {
            snprintf(payload, 256, "[%d] %s", bus->recent_errno, modbus_strerror(bus->recent_errno));
        }
    }

    wa_req_env_handover_payload(req_env, response, fmt, payload_len, (const char **)&payload);
}




void plugin_res_mb (restful_request_t *request, REQ_ENV_HANDLE req_env)
{
    int action = request->action;
    int fmt = request->payload_fmt;
    const char* query = request->query; 
    const char* url_path = (const char*)request->url;
    mb_coding_e coding = Coding_None;    
    int fc=-1, reg_addr=0,item_cnt=1, opaque = 0;
    char buf[128] = {0};
    char alias[128];
    int code = BAD_REQUEST_4_00;
    char error_reason[128] = {0};
    i_modbus_device_t * device;

    if((action == T_Put || action == T_Post) && request->payload_len == 0)
    {
        snprintf(error_reason, sizeof(error_reason), "No payload for write operation");
        goto end;
    }

    if(query == NULL)
    {
        snprintf(error_reason, sizeof(error_reason), "No query for write operation");    
        goto end;
    }

    int uri_query_len = strlen(query);
    if(!find_key_value((char*)query, uri_query_len, "DI", alias, sizeof(alias), '&'))
    {
        snprintf(error_reason, sizeof(error_reason), "[DI] is not provided");
        goto end;
    }
    device = find_mb_device(alias);
    if(device == NULL)
    {
        snprintf(error_reason, sizeof(error_reason), "Device ID [%s] is not configured", alias);
        goto end;
    }

    if(!find_key_value((char*)query, uri_query_len, "reg", buf, sizeof(buf), '&'))
    {
        snprintf(error_reason, sizeof(error_reason), "Query key [reg] is not provided");
        goto end;
    }
    reg_addr = atoi(buf);

    if(find_key_value((char*)query, uri_query_len, "area", buf, sizeof(buf), '&'))
    {
        mb_reg_type_e reg_type = check_mb_reg_type(buf);
        if(reg_type != MB_Invalid_Reg_Type)
            fc = get_reg_type_fc(reg_type, action != T_Get);
        else
        {
            snprintf(error_reason, sizeof(error_reason), "area [%s] is not valid", buf);
            goto end;
        }
    }
    else
    {
        if(action == T_Get)
            fc = MODBUS_FC_READ_HOLDING_REGISTERS;
        else
            fc = MODBUS_FC_WRITE_SINGLE_REGISTER;
    }

    if(find_key_value((char*)query, uri_query_len, "ITEMS", buf, sizeof(buf), '&'))
    {
        item_cnt = atoi(buf);
    }
    
    if(find_key_value((char*)query, uri_query_len, "coding", buf, sizeof(buf), '&'))
    {
        coding = parse_modbus_coding(buf);
        if (!MB_CODING_IS_VALID(coding))
        {
            TraceV(FLAG_MODBUS_ACCESS, "Invalid coding: %s",buf);
            snprintf(error_reason, sizeof(error_reason), "[coding] is not valid");
            goto end;
        }
    }

    code = INTERNAL_SERVER_ERROR_5_00;
    if(device->bus->status == S_Bus_Disconnected)
    {
        if(device->bus->drop_msgs % 100 == 0)
        {
            WARNING("msg drop for device %s disconnect. total drop: %u", 
                alias, device->bus->drop_msgs+1);
        }
        else
        {
            TraceV(FLAG_MB_CMD_ERROR,"drop /mb request on %s disconnected dev", alias);
        }
        device->bus->drop_msgs ++;        
         
        snprintf(error_reason, sizeof(error_reason), "Bus not connected");
        goto end;
    }

    i_modbus_command_t req[1];
    memset(req, 0, sizeof(i_modbus_command_t));
    req->bus= device->bus;
    req->slave_id = device->slave_id;
    req->fc = fc;
    req->fmt = fmt;
    req->addr=reg_addr;
    req->item_cnt = item_cnt;
    req->coding = coding;
    req->data_len = request->payload_len;
    req->data = (char*)request->payload;

   handle_modbus_command(req, device->bus, req_env);
   return;

end:
    wa_set_response(req_env, code, TEXT_PLAIN, strlen(error_reason), error_reason);
    return;

}